/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/pci.h>
#include <asm/byteorder.h>
#include <linux/interrupt.h>
#include <linux/firmware.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>

#include <alga/rng_mng.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/amd/atombios/atb.h>
#include <uapi/alga/amd/dce6/dce6.h>

#include "mc.h"
#include "rlc.h"
#include "ih.h"
#include "fence.h"
#include "ring.h"
#include "dmas.h"
#include "ba.h"
#include "cps.h"
#include "gpu.h"
#include "drv.h"

#include "bif.h"

#include "regs.h"

static u32 rr32_pcie(struct pci_dev *dev, u32 reg)
{
	wr32(dev, set(PI_PCIE_IDX, reg), PCIE_IDX);
	return rr32(dev, PCIE_DATA);
}

static void wr32_pcie(struct pci_dev *dev, u32 reg, u32 val)
{
	wr32(dev, set(PI_PCIE_IDX, reg), PCIE_IDX);
	wr32(dev, val, PCIE_DATA);
}

void bif_hdp_ena(struct pci_dev *dev)
{
	wr32(dev, BHE_HDP_RD_ENA | BHE_HDP_WR_ENA, BIF_HDP_ENA);
}

void bif_hdp_dis(struct pci_dev *dev)
{
	wr32(dev, 0, BIF_HDP_ENA);
}

void bif_mgls_dis(struct pci_dev *dev)
{
	u32 cur;
	u32 want;

	cur = rr32_pcie(dev, PCIE_CTL_1);
	want = cur | PC_SLV_MEM_LS_ENA | PC_SLV_MEM_AGGRESSIVE_LS_ENA
				| PC_MST_MEM_LS_ENA | PC_REPLAY_MEM_LS_ENA;
	if (cur != want)
		wr32_pcie(dev, want, PCIE_CTL_1);
}

void bif_mgls_ena(struct pci_dev *dev)
{
	u32 cur;
	u32 want;
	
	cur = rr32_pcie(dev, PCIE_CTL_1);
	want = cur | PC_SLV_MEM_LS_ENA | PC_SLV_MEM_AGGRESSIVE_LS_ENA
				| PC_MST_MEM_LS_ENA | PC_REPLAY_MEM_LS_ENA;
	if (cur != want)
		wr32_pcie(dev, want, PCIE_CTL_1);
}

u8 bif_pcie_gen_get(struct pci_dev *dev)
{
	u32 pcie_lc_speed_ctl;

	wr32(dev, PCIE_LC_SPEED_CTL, PCIE_PORT_IDX);
	pcie_lc_speed_ctl = rr32(dev, PCIE_PORT_DATA);
	return get(PLSC_LC_CURRENT_DATA_RATE, pcie_lc_speed_ctl);
}

/* the "gui" should be idle */
u8 bif_pcie_lanes_n_get(struct pci_dev *dev)
{
	u32 lc_link_width_ctl;
	u8 lc_link_width;

	wr32(dev, PCIE_LC_LINK_WIDTH_CTL, PCIE_PORT_IDX);
	lc_link_width_ctl = rr32(dev, PCIE_PORT_DATA);

	lc_link_width = get(PLLWC_LC_LINK_WIDTH, lc_link_width_ctl);

	switch (lc_link_width) {
	case PLLWC_LC_LINK_WIDTH_X1:
		return 1;
	case PLLWC_LC_LINK_WIDTH_X2:
		return 2;
	case PLLWC_LC_LINK_WIDTH_X4:
		return 4;
	case PLLWC_LC_LINK_WIDTH_X8:
		return 8;
	case PLLWC_LC_LINK_WIDTH_X12:
		/* not actually supported */
		return 12;
	case PLLWC_LC_LINK_WIDTH_X0:
	case PLLWC_LC_LINK_WIDTH_X16:
	default:
		return 16;
	}
}

long bif_pcie_root_speeds_get(struct pci_dev *dev, u8 *mask)
{
	struct pci_dev *root;
	u32 lnkcap;
	u32 lnkcap2;

	*mask = 0;
	root = dev->bus->self;

	/* we've been informed via and serverworks don't make the cut */
	if (root->vendor == PCI_VENDOR_ID_VIA ||
				    root->vendor == PCI_VENDOR_ID_SERVERWORKS)
		return -SI_ERR;

	/* ignore return values, lnkcaps will be set to 0 if errors occur */
	pcie_capability_read_dword(root, PCI_EXP_LNKCAP, &lnkcap);
	pcie_capability_read_dword(root, PCI_EXP_LNKCAP2, &lnkcap2);

	if (lnkcap2) {	/* pcie r3.0-compliant */
		if (lnkcap2 & PCI_EXP_LNKCAP2_SLS_2_5GB)
			*mask |= BIF_PCIE_ROOT_GEN_1;
		if (lnkcap2 & PCI_EXP_LNKCAP2_SLS_5_0GB)
			*mask |= BIF_PCIE_ROOT_GEN_2;
		if (lnkcap2 & PCI_EXP_LNKCAP2_SLS_8_0GB)
			*mask |= BIF_PCIE_ROOT_GEN_3;
	} else {	/* pcie pre-r3.0 */
		if (lnkcap & PCI_EXP_LNKCAP_SLS_2_5GB)
			 *mask = BIF_PCIE_ROOT_GEN_1;
		if (lnkcap & PCI_EXP_LNKCAP_SLS_5_0GB)
			 *mask = BIF_PCIE_ROOT_GEN_1 | BIF_PCIE_ROOT_GEN_2;
	}
	return 0;
}
